using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using N = ChessEngine.Native;

[assembly: CLSCompliant(true)]

namespace ChessEngine.Engine
{
    public sealed class Engine : N.IEngine
    {
        internal Board ChessBoard;
        internal Board PreviousChessBoard;

        internal Strategy WhiteStrategy;
        internal Strategy BlackStrategy;

        public ChessPieceColor HumanPlayer;

        public ChessPieceColor WhoseMove
        {
            get { return ChessBoard.WhoseMove; }
            set { ChessBoard.WhoseMove = value; }
        }

        public Engine()
        {           
            InitiateBoard("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
        }

        public Engine(string fen)
        {
            InitiateBoard(fen);
        }

        public bool IsWhiteMate
        {
            get
            {
                return ChessBoard.WhiteMate;
            }
        }
        public bool IsBlackMate
        {
            get
            {
                return ChessBoard.BlackMate;
            }
        }
        public bool IsCheckmate
        {
            get
            {
                return IsWhiteMate || IsBlackMate;
            }
        }
        public bool IsStalemate
        {
            get
            {
                return ChessBoard.StaleMate;
            }
        }
        public bool IsGameOver
        {
            get
            {
                return IsCheckmate || IsStalemate;
            }
        }

        public byte[] GetEnPassantMoves()
        {
            if (ChessBoard == null)
            {
                return null;
            }

            var returnArray = new byte[2];

            returnArray[0] = (byte)(ChessBoard.EnPassantPosition % 8);
            returnArray[1] = (byte)(ChessBoard.EnPassantPosition / 8);

            return returnArray;
        }

        public bool IsValidMove(byte sourceColumn, byte sourceRow, byte destinationColumn, byte destinationRow)
        {
            if (ChessBoard == null)
            {
                return false;
            }

            if (ChessBoard.Squares == null)
            {
                return false;
            }

            byte index = GetBoardIndex(sourceColumn, sourceRow);

            if (ChessBoard.Squares[index].Piece == null)
            {
                return false;
            }

            foreach (byte bs in ChessBoard.Squares[index].Piece.ValidMoves)
            {
                if (bs % 8 == destinationColumn)
                {
                    if ((byte)(bs / 8) == destinationRow)
                    {
                        return true;
                    }
                }
            }

            index = GetBoardIndex(destinationColumn, destinationRow);

            if (index == ChessBoard.EnPassantPosition)
            {
                return true;
            }

            return false;
        }

        public ChessPieceType GetPieceTypeAt(byte boardColumn, byte boardRow)
        {
            byte index = GetBoardIndex(boardColumn, boardRow);

            if (ChessBoard.Squares[index].Piece == null)
            {
                return ChessPieceType.None;
            }

            return ChessBoard.Squares[index].Piece.PieceType;
        }

        public ChessPieceType GetPieceTypeAt(byte index)
        {
            if (ChessBoard.Squares[index].Piece == null)
            {
                return ChessPieceType.None;
            }

            return ChessBoard.Squares[index].Piece.PieceType;
        }

        public ChessPieceColor GetPieceColorAt(byte boardColumn, byte boardRow)
        {
            byte index = GetBoardIndex(boardColumn, boardRow);

            if (ChessBoard.Squares[index].Piece == null)
            {
                return ChessPieceColor.White;
            }
            return ChessBoard.Squares[index].Piece.PieceColor;
        }

        public ChessPieceColor GetPieceColorAt(byte index)
        {
            if (ChessBoard.Squares[index].Piece == null)
            {
                return ChessPieceColor.White;
            }
            return ChessBoard.Squares[index].Piece.PieceColor;
        }

        public bool GetChessPieceSelected(byte boardColumn, byte boardRow)
        {
            byte index = GetBoardIndex(boardColumn, boardRow);

            if (ChessBoard.Squares[index].Piece == null)
            {
                return false;
            }

            return ChessBoard.Squares[index].Piece.Selected;
        }

        public byte[][] GetValidMoves(byte boardColumn, byte boardRow)
        {
            byte index = GetBoardIndex(boardColumn, boardRow);

            if (ChessBoard.Squares[index].Piece ==
                null)
            {
                return null;
            }

            var returnArray = new byte[ChessBoard.Squares[index].Piece.ValidMoves.Count][];
            int counter = 0;

            foreach (byte square in ChessBoard.Squares[index].Piece.ValidMoves)
            {
                returnArray[counter] = new byte[2];
                returnArray[counter][0] = (byte)(square % 8);
                returnArray[counter][1] = (byte)(square / 8);
                counter++;
            }

            return returnArray;
        }

        public void SetChessPieceSelection(byte boardColumn, byte boardRow,
                                          bool selection)
        {
            byte index = GetBoardIndex(boardColumn, boardRow);

            if (ChessBoard.Squares[index].Piece == null)
            {
                return;
            }
            //if (ChessBoard.Squares[index].Piece.PieceColor != HumanPlayer)
            //{
            //    return;
            //}
            if (ChessBoard.Squares[index].Piece.PieceColor != WhoseMove)
            {
                return;
            }
            ChessBoard.Squares[index].Piece.Selected = selection;
        }

        public bool MovePiece(byte sourceColumn, byte sourceRow, byte destinationColumn, byte destinationRow)
        {
            byte srcPosition = (byte)(sourceColumn + (sourceRow * 8));
            byte dstPosition = (byte)(destinationColumn + (destinationRow * 8));

            Piece piece = ChessBoard.Squares[srcPosition].Piece;

            PreviousChessBoard = new Board(ChessBoard);
            

            Board.MovePiece(ChessBoard, srcPosition, dstPosition, ChessPieceType.Queen);

            PieceValidMoves.GenerateValidMoves(ChessBoard);
           

            //If there is a check in place, check if this is still true;
            if (piece.PieceColor == ChessPieceColor.White)
            {
                if (ChessBoard.WhiteCheck)
                {
                    //Invalid Move
                    ChessBoard = new Board(PreviousChessBoard);
                    PieceValidMoves.GenerateValidMoves(ChessBoard);
                    return false;
                }
            }
            else if (piece.PieceColor == ChessPieceColor.Black)
            {
                if (ChessBoard.BlackCheck)
                {
                    //Invalid Move
                    ChessBoard = new Board(PreviousChessBoard);
                    PieceValidMoves.GenerateValidMoves(ChessBoard);
                    return false;
                }
            }

            return true;

        }

        public void ComputeAIMove(out byte srcCol, out byte srcRow, out byte destCol, out byte destRow)
        {
            Strategy strat = null;
            if (WhoseMove == ChessPieceColor.White)
            {
                if (WhiteStrategy == null)
                    WhiteStrategy = Strategy.Default();
                strat = WhiteStrategy;
            }
            else
            {
                if (BlackStrategy == null)
                    BlackStrategy = Strategy.Default();
                strat = BlackStrategy;
            }

            strat.MakeNextMove(this).Unpack(out srcCol, out srcRow, out destCol, out destRow);
        }

        public ChessPieceColor? CompeteAIStrategies()
        {
            while (!IsGameOver)
            {
                byte srcCol, srcRow, dstCol, dstRow;

                ComputeAIMove(out srcCol, out srcRow, out dstCol, out dstRow);

                MovePiece(srcCol, srcRow, dstCol, dstRow);
            }

            if (IsWhiteMate)
                return ChessPieceColor.White;
            else if (IsBlackMate)
                return ChessPieceColor.Black;
            else
                return null;
        }

        private void InitiateBoard(string fen)
        {
            HumanPlayer = ChessPieceColor.White;
            ChessBoard = new Board(fen);
            ChessBoard.WhoseMove = ChessPieceColor.White;
            PieceMoves.InitiateChessPieceMotion();
            GenerateValidMoves();
        }


        private void GenerateValidMoves()
        {
            PieceValidMoves.GenerateValidMoves(ChessBoard);
        }

        private static byte GetBoardIndex(byte BoardColumn, byte BoardRow)
        {
            return (byte)(BoardColumn + (BoardRow * 8));
        }


        N.PieceColor N.IEngine.WhoseMove
        {
            get
            {
                return (N.PieceColor)(int)WhoseMove;
            }
        }

        List<N.Move> N.IEngine.GetMoves()
        {
            List<N.Move> moves = new List<N.Move>();

            for (int sq = 0; sq < 64; ++sq)
            {
                Square square = ChessBoard.Squares[sq];

                if (square.Piece == null || square.Piece.PieceColor != WhoseMove)
                    continue;

                moves.AddRange(from dst in square.Piece.ValidMoves
                               select new N.Move { SrcPos1 = (byte)sq, DestPos1 = dst });
            }

            return moves;
        }

        void N.IEngine.DoMove(N.Move m)
        {
            MovePiece((byte)(m.SrcPos1 % 8), (byte)(m.SrcPos1 / 8),
                      (byte)(m.DestPos1 % 8), (byte)(m.DestPos1 / 8));
        }

        N.Move N.IEngine.DoAIMove()
        {
            byte srcCol, srcRow, destCol, destRow;

            ComputeAIMove(out srcCol, out srcRow, out destCol, out destRow);

            return new N.Move
            {
                SrcPos1 = (byte)(srcRow * 8 + srcCol),
                DestPos1 = (byte)(destRow * 8 + destCol)
            };
        }

        N.Piece N.IEngine.GetPieceAt(int col, int row)
        {
            Piece p = ChessBoard.Squares[row * 8 + col].Piece;

            if (p == null)
                return new N.Piece { Color = N.PieceColor.None, Type = N.PieceType.None };

            return new N.Piece { Color = (N.PieceColor)p.PieceColor, Type = (N.PieceType)p.PieceType };
        }

        N.PieceType N.IEngine.GetPieceTypeAt(int col, int row)
        {
            return ((N.IEngine)this).GetPieceAt(col, row).Type;
        }

        N.PieceColor N.IEngine.GetPieceColorAt(int col, int row)
        {
            return ((N.IEngine)this).GetPieceAt(col, row).Color;
        }

        void IDisposable.Dispose()
        {
        }
    }
}